{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AtspjpqElmtO"
      },
      "source": [
        "# Inference Speedup"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wKxd_dT3lmtQ"
      },
      "source": [
        "## Seminar\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ICY7yJ3TlmtQ"
      },
      "source": [
        "### Plan\n",
        "\n",
        "- Analyze the proof of distribution preservation in SpecDec + Sampling\n",
        "- Create an introduction to Triton GeMM\n",
        "  - General introduction, comparison with CUDA\n",
        "  - Blocks/Threads vs Program\n",
        "- Show execution times of GeMV, GeMM kernels and their scaling with batch size\n",
        "  - Emphasize the execution time difference between DecodeMany vs Decode (DecodeMany == bs > 1. Almost no difference up to batch size 64 with Decode)\n",
        "- Highlight how autotune affects performance during the decoding step\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### SpecDec + Sampling\n",
        "\n",
        "_Special thanks to Lesha Glikin._"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Introduction:\n",
        "- We consider one iteration of verification of a single draft token\n",
        "- $V$ - set of all tokens\n",
        "- $p$ - probability function of the target model token ($p: V \\rightarrow [0,1]$)\n",
        "- $q$ - probability function of the draft model token\n",
        "\n",
        "Let's assume the token acceptance procedure works as follows:\n",
        "- Sample token $t_q$ from $q$\n",
        "- Based on the (stochastic!) acceptance procedure, decide whether to accept or reject $t_q$\n",
        "- If rejected, sample a new token from the adjusted distribution of the target model (to be defined later)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "**Formalization**:\n",
        "\n",
        "- $t_q \\sim q$ - token from the draft model\n",
        "- $t_{\\tilde p} \\sim \\tilde p $ - token from the adjusted distribution of the target model ($\\tilde p$ is unknown yet)\n",
        "- $r \\sim R$ - source of randomness (unknown yet)\n",
        "- $t_q ⫫ t_{\\tilde p } ⫫ r$\n",
        "- $t$ - the final token resulting from the verification step, defined as\n",
        "  $$t = \\begin{cases} t_q,\\ A(t_q, r) \\\\ t_{\\tilde p },\\ \\lnot A(t_q, r) \\end{cases}$$\n",
        "where $A(r, t_q)$ - the event of accepting/rejecting the token."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "**Desirable properties:** we want to find such $\\tilde p, A, R$ that:\n",
        "- $t$ has the same distribution as $p$\n",
        "    - because we want to preserve the distribution of the base model.\n",
        "- $A(r, t_q)$ is maximized\n",
        "    - because we want to maximize the probability of accepting the token."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Let's understand how the probability function $t$ works:\n",
        "\n",
        "$$ P(t = T) = P(t = T \\wedge A(t_q, r)) + P(t = T \\wedge \\lnot A(t_q, r)) $$\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<details>\n",
        "<summary>What is the first term equivalent to?</summary>\n",
        "\n",
        "-  $t = T \\wedge A(t_q, r)$\n",
        "- Given that the token was accepted, $t = t_q$ (by definition of $t$), so in the left event we can substitute:\n",
        "    -  $\\iff t_q = T \\wedge A(t_q, r)$\n",
        "- Given that $t_q = T$, in the right event we can substitute it:\n",
        "    - $\\iff t_q = T \\wedge A(T, r)$\n",
        "- we now have a conjunction of independent events, therefore\n",
        "    - $P(t_q = T \\wedge A(T, r)) = q(T) \\cdot P(A(T, r))$\n",
        "\n",
        "</details>  "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<details>\n",
        "<summary>(The same more rigorously, for fans of sigma-algebras)</summary>\n",
        "\n",
        "Consider a probability space $(\\Omega, \\mathcal{F}, P)$. All random variables $t, t_q, t_{\\tilde p}, r, A$ are defined on it. $A(...)$ is a function that takes values 0 and 1.\n",
        "\n",
        "- Consider all $\\omega_0 \\in \\Omega$ such that \n",
        "    - $t(\\omega_0) = T \\wedge A(t_q(\\omega_0), r(\\omega_0)) = 1$.\n",
        "\n",
        "- for all $\\omega_0\\colon A(t_q(\\omega_0), r(\\omega_0)) = 1$, we have $t(\\omega_0) = t_q(\\omega_0)$ -- simply by definition of $t$. Therefore, in the left event we can substitute:\n",
        "    -  $\\iff t_q(\\omega_0) = T \\wedge A(t_q(\\omega_0), r(\\omega_0)) = 1$\n",
        "- on the other hand, for all $\\omega_0$ it is additionally true that $\\colon t(\\omega_0) = T$. But this means that on this subset of omegas, $t_q$ is identically equal to $T$. Therefore, in the right event we can substitute:\n",
        "    - $\\iff t_q(\\omega_0) = T \\wedge A(T, r(\\omega_0)) = 1$\n",
        "- we now have a conjunction of independent events. Consequently, for all $\\omega_0 \\in \\Omega$ such that $t(\\omega_0) = T \\wedge A(t_q(\\omega_0), r(\\omega_0)) = 1$, it holds that:\n",
        "    - $P(t_q = T \\wedge A(T, r)) = q(T) \\cdot P(A(T, r))$\n",
        "\n",
        "</details>  "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n",
        "\n",
        "<details>\n",
        "<summary>What is the second term equivalent to?</summary>\n",
        "\n",
        "-  $t = T \\wedge \\lnot A(t_q, r)$\n",
        "- Since the token was not accepted, $t = t_{\\tilde p}$ (again by definition of $t$), so we can substitute\n",
        "    - $\\iff t_{\\tilde p} = T \\wedge \\lnot A(t_q, r)$\n",
        "- we now have a conjunction of independent events\n",
        "    - $P(t_{\\tilde p} = T \\wedge \\lnot A(t_q, r)) = \\tilde p(T) \\cdot \\left( 1 - P(A(t_q, r))\\right)$\n",
        "\n",
        "</details>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n",
        "<details>\n",
        "<summary>Overall, the token's probability can be written down as:</summary>\n",
        "\n",
        "   $$P(t = T) = q(T) \\cdot P(A(T, r)) + \\tilde p(T) \\cdot \\left(1 - P(A(t_q, r))\\right)$$\n",
        "\n",
        "</details>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The requirement that $t \\sim p$ is expressed as:\n",
        "$$\n",
        "\\forall\\ T \\in V,\\ p(t) = P(t = T)\n",
        "$$\n",
        "\n",
        "How do we now express $\\tilde p(t)$?\n",
        "<details>\n",
        "<summary>Answer:</summary>\n",
        "\n",
        "$$\n",
        "\\iff \\forall\\ T \\in V,\\ \\tilde p (T) = \\frac{p(T) - q(T) P(A(T, r))}{1 - P(A(t_q, r))}\n",
        "= \\frac{p(T) - q(T) \\cdot P(A(T, r))}{\\sum_{X \\in V} \\Bigl(p(X) - q(X) \\cdot P(A(X, r))\\Bigr)}\n",
        "$$\n",
        "\n",
        "</details>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "This expression uniquely defines $\\tilde p$ for fixed $A$ and $R$, and we only need the non-negativity of th numerators:\n",
        "$$\n",
        "p(T) - q(T) \\cdot P(A(T, r)) \\geq 0\n",
        "$$\n",
        "<details>\n",
        "<summary>Which is equivalent to:</summary>\n",
        "\n",
        "$$\n",
        "\\begin{align}\n",
        "&P(A(T, r)) \\leq \\frac{p(T)}{q(T)}\n",
        "\\\\\n",
        "\\iff &P(A(T, r)) \\leq \\min\\left(1, \\frac{p(T)}{q(T)}\\right)\n",
        "\\end{align}\n",
        "$$\n",
        "\n",
        "</details>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "How to choose $A, R$ so that the condition above is satisfied for all $T$, and to maximize $P(A(T, r))$?\n",
        "<details>\n",
        "<summary>Answer:</summary>\n",
        "\n",
        "We can use $R = U[0, 1]$ and $A(T, r) = I\\left(r < \\frac{p(T)}{q(T)}\\right)$.\n",
        "\n",
        "Then let's write out what $\\tilde p$ will be equal to:\n",
        "$$\n",
        "\\tilde p (T) = \\frac{p(T) - q(T) \\cdot P(A(T, r))}{\\sum_{X \\in U} p(X) - q(X) \\cdot P(A(X, r))}\n",
        "= \\frac{p(T) - q(T) \\cdot \\min \\left(1, \\frac{p(T)}{q(T)}\\right)}{\\ldots} = \\frac{\\max\\left(p(T) - q(T), 0\\right)}{\\ldots}\n",
        "$$\n",
        "</details>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Triton GeMM: a bit deeper."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "#### Catch activation and weight"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "axGKC6hXlmtR",
        "outputId": "553dca24-fd13-411a-a322-e7cb48733b10"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "torch version: 2.5.1+cu124\n",
            "triton version: 3.1.0\n"
          ]
        }
      ],
      "source": [
        "import time\n",
        "from typing import List\n",
        "import os\n",
        "os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"0\"\n",
        "\n",
        "import torch\n",
        "import torch.nn as nn\n",
        "\n",
        "import triton\n",
        "import triton.language as tl\n",
        "\n",
        "print(\"torch version:\", torch.__version__)\n",
        "print(\"triton version:\", triton.__version__)\n",
        "\n",
        "from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0,
          "referenced_widgets": [
            "61621d16e2cd40c3ad0c6a849a8b8264",
            "6bed81d4616649bd857418a131538f7d",
            "9ffb3a0c0fea4081bf4f7c5e6218758f",
            "3838c3cdee884f6d902e3b5d0d4e4583",
            "7c3c03fa91874ae5ac941ed171eeb3ea",
            "7498bbbbae7048119584dea4a914da0a",
            "6272889cfea04dab834ee1c62817540b",
            "5f922ecfa23c4705a42336c1e79900df",
            "7f1074b6abde4dddb7e50f67dc9a99b3",
            "87caf0f9f3dc4b508d7733e66fde5407",
            "c0e17b91d9b244daaf63f87f4683503e"
          ]
        },
        "id": "UI2qK8TdlmtT",
        "outputId": "23e582d5-f21a-4fd4-b1d9-13470ba365e3"
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "/usr/local/lib/python3.11/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n",
            "The secret `HF_TOKEN` does not exist in your Colab secrets.\n",
            "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n",
            "You will be able to reuse this secret in all of your notebooks.\n",
            "Please note that authentication is recommended but still optional to access public models or datasets.\n",
            "  warnings.warn(\n"
          ]
        },
        {
          "data": {
            "application/vnd.jupyter.widget-view+json": {
              "model_id": "61621d16e2cd40c3ad0c6a849a8b8264",
              "version_major": 2,
              "version_minor": 0
            },
            "text/plain": [
              "Loading checkpoint shards:   0%|          | 0/2 [00:00<?, ?it/s]"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "model_name = \"Qwen/Qwen2.5-3B-Instruct\"\n",
        "\n",
        "model = AutoModelForCausalLM.from_pretrained(\n",
        "    model_name,\n",
        "    torch_dtype=torch.float16,\n",
        "    device_map=\"auto\"\n",
        ")\n",
        "tokenizer = AutoTokenizer.from_pretrained(model_name)\n",
        "streamer = TextStreamer(tokenizer, skip_prompt=True)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "id": "5GbVOeSXlmtT"
      },
      "outputs": [],
      "source": [
        "# challenging input\n",
        "prompt = \"\"\"\n",
        "# SYSTEM PREAMBLE\n",
        "1) You are an excellent Python software developer with over 10 years of experience. You have a strong understanding of Python related topics, data structures, libraries, frameworks, algorithms, best practices and optimization techniques.\n",
        "2) You are here to help the user (the software developer) by breaking his request in ## TASK into logical steps and writing high-quality and efficient code to implement each step.\n",
        "3) You have to return the entire code.\n",
        "4) Follow \"Answering rules\" without exception.\n",
        "\n",
        "## ANSWERING RULES\n",
        "1) Repeat the question before answering it.\n",
        "2) Always follow \"CHAIN OF THOUGHTS\" to execute the task.\n",
        "\n",
        "## CHAIN OF THOUGHTS\n",
        "1) **OBEY the EXECUTION MODE**\n",
        "2) **TASK ANALYSIS:**\n",
        "   - Understand the user's request thoroughly.\n",
        "   - Identify the key components and requirements of the task.\n",
        "3) **PLANNING: CODDING:**\n",
        "   - Break down the task into logical, sequential steps.\n",
        "   - Outline the strategy for implementing each step.\n",
        "4) **CODING:**\n",
        "   - Explain your thought process before writing any code.\n",
        "   - Write the entire code for each step, ensuring it is clean, optimized, and well-commented.\n",
        "   - Handle edge cases and errors appropriately.\n",
        "5) **VERIFICATION:**\n",
        "   - Review the complete code solution for accuracy and efficiency.\n",
        "   - Ensure the code meets all requirements and is free of errors.\n",
        "\n",
        "## TASK\n",
        "\n",
        "Write a python function that receives the following JSON as input and enters data from it into the Google Sheet.\n",
        "\n",
        "{\n",
        "    'date': '31-05-2024',\n",
        "    'revenue': 90000,\n",
        "    'person' : 'User1',\n",
        "    'expensesList': [30000, 14000, 10000, 2000, 15000],\n",
        "    'expensesDescList': [ 'Ключи', 'Ключи2', 'Счет за такси', 'Клей, пластины', 'Провод 40м'],\n",
        "    'expensesTypeList': ['Закупки', 'Закупки', 'Расходы', 'Ремонт', 'Ремонт']\n",
        "}\n",
        "\n",
        "There is a date in JSON, you can use it to determine the month.\n",
        "The data is entered into a list with the name of the month. If such a list does not exist yet, then you need to create a list with a new month inside the sheet.\n",
        "\n",
        "The list should have the following columns (the first rows are used as headings):\n",
        "A1: Дата расхода,\n",
        "B1: сумма расхода,\n",
        "C1: описание расхода,\n",
        "D1: тип расхода,\n",
        "E1: кто внес данные\n",
        "\n",
        "G1: Дата выручки\n",
        "H1: Сумма выручки\n",
        "I1: Кто внес данные\n",
        "\n",
        "Please separate expenses and profits with a blank column.\n",
        "Please sort expenses by date, including those already listed in Google sheet list.\n",
        "Please sort earnings by date, including those already listed in Google sheet list.\n",
        "\n",
        "It is prohibited to use oauth2client as it is deprecated.\n",
        "\"\"\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "scDTImShlmtU",
        "outputId": "550cdf9f-6118-46c1-83ea-ab4b3c1059ef"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "To accomplish the task, we will follow these steps:\n",
            "\n",
            "1. Parse the JSON data.\n",
            "2. Create or update the expense and profit lists in the Google Sheet.\n",
            "3. Sort both lists by date.\n",
            "4. Insert the data into the appropriate columns in the Google Sheet.\n",
            "\n",
            "Let's start by parsing the JSON data and creating the necessary lists. For simplicity, we will assume that the Google Sheet has been set up correctly and that we have access to the necessary Google Sheets API client library. We'll use `google-auth` and `google-api-python-client` for authentication and interaction with the Google Sheets API.\n",
            "\n",
            "Here's the complete code:\n",
            "\n",
            "```python\n",
            "import json\n",
            "from datetime import datetime\n",
            "from google.oauth2.credentials import Credentials\n",
            "from googleapiclient.discovery import build\n",
            "\n",
            "def parse_json(json_data):\n",
            "    \"\"\"Parse the JSON data.\"\"\"\n",
            "    parsed_data = json.loads(json_data)\n",
            "    \n",
            "    # Extract relevant information\n",
            "    date_str = parsed_data['date']\n",
            "    revenue = parsed_data['revenue']\n",
            "    person = parsed_data['person']\n",
            "    expenses = parsed_data['expensesList']\n",
            "    expenses_desc = parsed_data['expensesDescList']\n",
            "    expenses_type = parsed_data['expensesTypeList']\n",
            "    \n",
            "    return {\n",
            "        'date': date_str,\n",
            "        'revenue': revenue,\n",
            "        'person': person,\n",
            "        'expenses': expenses,\n",
            "        'expenses_desc': expenses_desc,\n",
            "        'expenses_type': expenses_type\n",
            "    }\n",
            "\n",
            "def get_sheet_service(credentials):\n",
            "    \"\"\"Get the Google Sheets service.\"\"\"\n",
            "    service = build('sheets', 'v4', credentials=credentials)\n",
            "    return service.spreadsheets()\n",
            "\n",
            "def add_row_to_sheet(sheet_id, service, row, sheet_name='Sheet1'):\n",
            "    \"\"\"Add a row to the specified sheet.\"\"\"\n",
            "    body = {\n",
            "        'values': [row]\n",
            "    }\n",
            "    result = service.spreadsheets().values().append(\n",
            "        spreadsheetId=sheet_id,\n",
            "        valueInputOption='USER_ENTERED',\n",
            "        range=f'{sheet_name}!A1:E'\n",
            "    ).execute()\n",
            "    print(f\"{result.get('updates').get('updatedCells')} cells updated.\")\n",
            "    return result\n",
            "\n",
            "def add_profit_row_to_sheet(sheet_id, service, row, sheet_name='Sheet1'):\n",
            "    \"\"\"Add a row for profit to the specified sheet.\"\"\"\n",
            "    body = {\n",
            "        'values': [row]\n",
            "    }\n",
            "    result = service.spreadsheets().values().append(\n",
            "        spreadsheetId=sheet_id,\n",
            "        valueInputOption='USER_ENTERED',\n",
            "        range=f'{sheet_name}!G1:I'\n",
            "    ).execute()\n",
            "    print(f\"{result.get('updates').get('updatedCells')} cells updated.\")\n",
            "    return result\n",
            "\n",
            "def get_date_month(date_str):\n",
            "    \"\"\"Get the month from the date string.\"\"\"\n",
            "    date_obj = datetime.strptime(date_str, '%d-%m-%Y')\n",
            "    return date_obj.strftime('%B')\n",
            "\n",
            "def main():\n",
            "    # Load credentials from JSON file\n",
            "    with open('path/to/your/credentials.json') as f:\n",
            "        credentials = Credentials.from_authorized_user_file(f)\n",
            "    \n",
            "    # Get the Google Sheets service\n",
            "    service = get_sheet_service(credentials)\n",
            "    \n",
            "    # Parse the JSON data\n",
            "    parsed_data = parse_json(json_data)\n",
            "    \n",
            "    # Determine the month\n",
            "    month = get_date_month(parsed_data['date'])\n",
            "    \n",
            "    # Prepare expense row\n",
            "    expenses_row = [\n",
            "        parsed_data['date'],\n",
            "        str(parsed_data['revenue']),\n",
            "        ', '.join(parsed_data['expenses_desc']),\n",
            "        ', '.join(parsed_data['expenses_type']),\n",
            "        parsed_data['person']\n",
            "    ]\n",
            "    \n",
            "    # Prepare profit row\n",
            "    profit_row = [\n",
            "        parsed_data['date'],\n",
            "        str(parsed_data['revenue']),\n",
            "        'Выручка',\n",
            "        'Себестоимость',\n",
            "        parsed_data['person']\n",
            "    ]\n",
            "    \n",
            "    # Add expense row to the sheet\n",
            "    add_row_to_sheet(service.spreadsheet_id, service, expenses_row, sheet_name='Sheet1')\n",
            "    \n",
            "    # Add profit row to the sheet\n",
            "    add_profit_row_to_sheet(service.spreadsheet_id, service, profit_row, sheet_name='Sheet1')\n",
            "\n",
            "if __name__ == \"__main__\":\n",
            "    json_data = '''\n",
            "    {\n",
            "        'date': '31-05-2024',\n",
            "        'revenue': 90000,\n",
            "        'person': 'User1',\n",
            "        'expensesList': [30000, 14000, 10000, 2000, 15000],\n",
            "        'expensesDescList': [ 'Ключи', 'Ключи2', 'Счет за такси', 'Клей, пластины', 'Провод 40м'],\n",
            "        'expensesTypeList': ['Закупки', 'Закупки', 'Расходы', 'Ремонт', 'Ремонт']\n",
            "    }\n",
            "    '''\n",
            "    main()\n",
            "```\n",
            "\n",
            "### Explanation:\n",
            "\n",
            "1. **Parsing JSON\n"
          ]
        }
      ],
      "source": [
        "messages = [\n",
        "    {\"role\": \"system\", \"content\": \"You are Qwen, created by Alibaba Cloud. You are a helpful assistant.\"},\n",
        "    {\"role\": \"user\", \"content\": prompt}\n",
        "]\n",
        "text = tokenizer.apply_chat_template(\n",
        "    messages,\n",
        "    tokenize=False,\n",
        "    add_generation_prompt=True\n",
        ")\n",
        "model_inputs = tokenizer([text], return_tensors=\"pt\").to(model.device)\n",
        "generation_output = model.generate(**model_inputs, streamer=streamer, max_new_tokens=1024)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "class Catcher(nn.Module):\n",
        "    def __init__(self, inps: List, module: nn.Module):\n",
        "        \"\"\"Use this module to wrap model layer to save its inputs.\n",
        "\n",
        "        Args:\n",
        "            inps (List): external inputs storage\n",
        "            module (nn.Module): module to wrap\n",
        "        \"\"\"\n",
        "        super().__init__()\n",
        "        \n",
        "        # YOUR CODE HERE\n",
        "        ...\n",
        "        \n",
        "\n",
        "    def forward(self, inp, **kwargs):\n",
        "        \"\"\"Save inps to storage and raise an exception ValueError\n",
        "\n",
        "        Args:\n",
        "            inp: inputs to save\n",
        "        \"\"\"\n",
        "        # YOUR CODE HERE\n",
        "        ..."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "id": "UaQPJeQMlmtV"
      },
      "outputs": [],
      "source": [
        "layer = model.model.layers[0]\n",
        "inps = []\n",
        "layer.self_attn.q_proj = Catcher(inps, layer.self_attn.q_proj) # wrap"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {
        "id": "lDXTyhi4lmtV"
      },
      "outputs": [],
      "source": [
        "try:\n",
        "    model(model_inputs.input_ids)\n",
        "except ValueError as e:\n",
        "    layer.self_attn.q_proj = layer.self_attn.q_proj.module"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "N4wTaZR5lmtV",
        "outputId": "9f494935-5a56-49fa-db61-2042d56ba9e7"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "torch.Size([1, 700, 2048])"
            ]
          },
          "execution_count": 9,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "inps[0].shape"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "id": "9p1tFHE5lmtV"
      },
      "outputs": [],
      "source": [
        "# unwrap\n",
        "weight = layer.self_attn.q_proj.weight"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pHW-5ajhlmtV"
      },
      "source": [
        "### Triton GeMM"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "#### When to Use Triton\n",
        "- Optimization Steps:\n",
        "1. Use torch.compile():\n",
        "    - Start by using torch.compile() to optimize your code.\n",
        "2. Adapt Your Code:\n",
        "    - Rewrite code to be more suitable for torch.compile().\n",
        "        - E.g., eliminate graph breaks to enable CUDA graphs.\n",
        "3. Profile and Identify Bottlenecks:\n",
        "    - Find slow parts of your code using profiling tools.\n",
        "    - Write custom Triton kernels for these parts.\n",
        "4. Consider CUDA:\n",
        "    - If still not fast enough, write custom CUDA kernels.\n",
        "\n",
        "**Note**: For maximum performance from the start, you may choose CUDA directly.\n",
        "\n",
        "#### Rough Edges in Triton\n",
        "- New-ish Project:\n",
        "    - Contains rough edges; code may not behave as expected.\n",
        "    - Expected to become more polished over time.\n",
        "- Recommendation:\n",
        "    - Debugging is important; use “simulator mode” when possible.\n",
        "    - Be aware of limitations on older GPUs or with certain operations."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Resources (most useful):\n",
        "- [GPU MODE Lecture 14: Practitioners Guide to Triton](https://christianjmills.com/posts/cuda-mode-notes/lecture-014/#auto-tuning) - contains basic description of Triton development, its pros/cons, practical examples with full explanations, and optimization techniques\n",
        "- [Flash-Decoding for long-context inference](https://pytorch.org/blog/flash-decoding/) - description of SPLIT_K optimization for faster inference during the decoding step by better utilizing the GPU\n",
        "\n",
        "Less useful but interesting:\n",
        "- [Deep Dive on the Hopper TMA Unit for FP8 GEMMs](https://pytorch.org/blog/hopper-tma-unit/) - about the importance of TMA unit for Hopper and BlackWell\n",
        "- [Persistent Matmul](https://triton-lang.org/main/getting-started/tutorials/09-persistent-matmul.html) - special version of GeMM for Hopper and BlackWell with TMA support\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {
        "id": "mH2g_HxalmtV"
      },
      "outputs": [],
      "source": [
        "def is_cuda():\n",
        "    return triton.runtime.driver.active.get_current_target().backend == \"cuda\"\n",
        "\n",
        "assert is_cuda(), \"CUDA only tutorial\"\n",
        "ref_lib = 'cuBLAS'"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "id": "I1Il52RRlmtW"
      },
      "outputs": [],
      "source": [
        "# TODO: play  with configs on seminar\n",
        "def get_cuda_autotune_config():\n",
        "    return [\n",
        "        triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 64, 'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8),\n",
        "        triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64,  'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 64,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64,  'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 32,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64,  'BLOCK_SIZE_N': 32,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5, num_warps=2),\n",
        "        triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 32,  'BLOCK_SIZE_N': 64,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5, num_warps=2),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32,  'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 64,  'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 128, 'GROUP_SIZE_M': 8}, num_stages=2, num_warps=4),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32,  'GROUP_SIZE_M': 16}, num_stages=4, num_warps=4),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 64,  'GROUP_SIZE_M': 16}, num_stages=3, num_warps=8),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 128, 'GROUP_SIZE_M': 16}, num_stages=2, num_warps=4),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 64,  'GROUP_SIZE_M': 16}, num_stages=4, num_warps=4),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 128, 'GROUP_SIZE_M': 16}, num_stages=3, num_warps=8),\n",
        "\t    triton.Config({'SPLIT_K': 1, 'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 256, 'GROUP_SIZE_M': 16}, num_stages=2, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 64, 'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64,  'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 64,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64,  'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 32,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64,  'BLOCK_SIZE_N': 32,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5, num_warps=2),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 32,  'BLOCK_SIZE_N': 64,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5, num_warps=2),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32,  'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "\t\ttriton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 64,  'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 128, 'GROUP_SIZE_M': 8}, num_stages=2, num_warps=4),\n",
        "\t\ttriton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32,  'GROUP_SIZE_M': 16}, num_stages=4, num_warps=4),\n",
        "\t\ttriton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 64,  'GROUP_SIZE_M': 16}, num_stages=3, num_warps=8),\n",
        "\t\ttriton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 128, 'GROUP_SIZE_M': 16}, num_stages=2, num_warps=4),\n",
        "\t\ttriton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 64,  'GROUP_SIZE_M': 16}, num_stages=4, num_warps=4),\n",
        "\t\ttriton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 128, 'GROUP_SIZE_M': 16}, num_stages=3, num_warps=8),\n",
        "\t\ttriton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 256, 'GROUP_SIZE_M': 16}, num_stages=2, num_warps=4),\n",
        "        # custom\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 16,  'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 16,  'BLOCK_SIZE_N': 64,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5, num_warps=2),\n",
        "        triton.Config({'SPLIT_K': 2, 'BLOCK_SIZE_M': 16, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 128, 'GROUP_SIZE_M': 16}, num_stages=2, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 4, 'BLOCK_SIZE_M': 16, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 256, 'GROUP_SIZE_M': 16}, num_stages=2, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 4, 'BLOCK_SIZE_M': 16,  'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 4, 'BLOCK_SIZE_M': 16,  'BLOCK_SIZE_N': 64,  'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5, num_warps=2),\n",
        "        triton.Config({'SPLIT_K': 4, 'BLOCK_SIZE_M': 16, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 128, 'GROUP_SIZE_M': 16}, num_stages=2, num_warps=4),\n",
        "        triton.Config({'SPLIT_K': 4, 'BLOCK_SIZE_M': 16, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 256, 'GROUP_SIZE_M': 16}, num_stages=2, num_warps=4),\n",
        "    ]\n",
        "\n",
        "\n",
        "def get_autotune_config():\n",
        "    if is_cuda():\n",
        "        return get_cuda_autotune_config()\n",
        "    raise NotImplementedError(\"ooops\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "id": "Qx7SUeeZlmtW"
      },
      "outputs": [],
      "source": [
        "@triton.autotune(\n",
        "    configs=get_autotune_config(),\n",
        "    key=['M', 'N', 'K'],\n",
        "    reset_to_zero=['c_ptr']\n",
        ")\n",
        "@triton.jit\n",
        "def matmul_kernel(\n",
        "    # Pointers to matrices\n",
        "    a_ptr, b_ptr, c_ptr,\n",
        "    # Matrix dimensions\n",
        "    M, N, K,\n",
        "    # The stride variables represent how much to increase the ptr by when moving by 1\n",
        "    # element in a particular dimension. E.g. `stride_am` is how much to increase `a_ptr`\n",
        "    # by to get the element one row down (A has M rows).\n",
        "    stride_am, stride_ak,\n",
        "    stride_bk, stride_bn,\n",
        "    stride_cm, stride_cn,\n",
        "    # Meta-parameters\n",
        "    BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,\n",
        "    GROUP_SIZE_M: tl.constexpr, SPLIT_K: tl.constexpr,\n",
        "):\n",
        "    \"\"\"Kernel for computing the matmul C = A x B.\n",
        "    A has shape (M, K), B has shape (K, N) and C has shape (M, N)\n",
        "    \"\"\"\n",
        "    # -----------------------------------------------------------\n",
        "    # Map program ids `pid` to the block of C it should compute.\n",
        "    # This is done in a grouped ordering to promote L2 data reuse.\n",
        "    # See above `L2 Cache Optimizations` section for details.\n",
        "    pid = tl.program_id(axis=0)\n",
        "    pid_sp_k = tl.program_id(axis=1)\n",
        "    num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)\n",
        "    num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)\n",
        "    num_pid_in_group = GROUP_SIZE_M * num_pid_n\n",
        "    group_id = pid // num_pid_in_group\n",
        "    first_pid_m = group_id * GROUP_SIZE_M\n",
        "    group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)\n",
        "    pid_m = first_pid_m + (pid % group_size_m)\n",
        "    pid_n = (pid % num_pid_in_group) // group_size_m\n",
        "\n",
        "    # ----------------------------------------------------------\n",
        "    # Create pointers for the first blocks of A and B.\n",
        "    # We will advance this pointer as we move in the K direction\n",
        "    # and accumulate\n",
        "    # `a_ptrs` is a block of [BLOCK_SIZE_M, BLOCK_SIZE_K] pointers\n",
        "    # `b_ptrs` is a block of [BLOCK_SIZE_K, BLOCK_SIZE_N] pointers\n",
        "    # See above `Pointer Arithmetics` section for details\n",
        "    offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M\n",
        "    offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N\n",
        "    offs_k = pid_sp_k * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K)\n",
        "    a_ptrs = a_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)\n",
        "    b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)\n",
        "    # -----------------------------------------------------------\n",
        "    # Iterate to compute a block of the C matrix.\n",
        "    # We accumulate into a `[BLOCK_SIZE_M, BLOCK_SIZE_N]` block\n",
        "    # of fp32 values for higher accuracy.\n",
        "    # `accumulator` will be converted back to fp16 after the loop.\n",
        "    accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)\n",
        "    for k in range(0, tl.cdiv(K, BLOCK_SIZE_K * SPLIT_K)):\n",
        "        # Load the next block of A and B, generate a mask by checking the K dimension.\n",
        "        # If it is out of bounds, set it to 0.\n",
        "        a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K * SPLIT_K, other=0.0)\n",
        "        b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K * SPLIT_K, other=0.0)\n",
        "        # We accumulate along the K dimension.\n",
        "        accumulator += tl.dot(a, b, allow_tf32=False)\n",
        "        # Advance the ptrs to the next K block.\n",
        "        a_ptrs += BLOCK_SIZE_K * SPLIT_K * stride_ak\n",
        "        b_ptrs += BLOCK_SIZE_K * SPLIT_K * stride_bk\n",
        "    # You can fuse arbitrary activation functions here\n",
        "    # while the accumulator is still in FP32!\n",
        "    c = accumulator.to(c_ptr.dtype.element_ty)\n",
        "    # -----------------------------------------------------------\n",
        "    # Write back the block of the output matrix C with masks.\n",
        "    offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)\n",
        "    offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)\n",
        "    c_ptrs = c_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]\n",
        "    c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)\n",
        "    if SPLIT_K == 1:\n",
        "        tl.store(c_ptrs, c, mask=c_mask)\n",
        "    else:\n",
        "        tl.atomic_add(c_ptrs, c, mask=c_mask)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "metadata": {
        "id": "0OuhzgOylmtW"
      },
      "outputs": [],
      "source": [
        "def matmul(a, b):\n",
        "    # Check constraints.\n",
        "    assert a.shape[1] == b.shape[0], \"Incompatible dimensions\"\n",
        "    M, K = a.shape\n",
        "    K, N = b.shape\n",
        "    # Allocates output.\n",
        "    c = torch.zeros((M, N), device=a.device, dtype=torch.float16)\n",
        "    grid = lambda META: (\n",
        "        triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(N, META['BLOCK_SIZE_N']),\n",
        "        META['SPLIT_K'],\n",
        "    )\n",
        "    matmul_kernel[grid](\n",
        "        a, b, c,\n",
        "        M, N, K,\n",
        "        a.stride(0), a.stride(1),\n",
        "        b.stride(0), b.stride(1),\n",
        "        c.stride(0), c.stride(1),\n",
        "    )\n",
        "    return c"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Y7THM_iRlmtW"
      },
      "source": [
        "Sanity check on quality:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Zy7cPBh1lmtW",
        "outputId": "0618602d-24f8-4b83-cf40-175cd154b203"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "✅ Triton and Torch match\n"
          ]
        }
      ],
      "source": [
        "torch.manual_seed(0)\n",
        "\n",
        "a = inps[0][0].to(torch.float16).cuda()\n",
        "b = weight.to(torch.float16).cuda()\n",
        "\n",
        "triton_output = matmul(a, b)\n",
        "torch_output = torch.matmul(a, b)\n",
        "\n",
        "if torch.allclose(triton_output, torch_output, atol=2e-2):\n",
        "    print(\"✅ Triton and Torch match\")\n",
        "else:\n",
        "    print(\"❌ Triton and Torch differ\")\n",
        "    print(f\"triton_output_with_fp16_inputs={triton_output}\")\n",
        "    print(f\"torch_output_with_fp16_inputs={torch_output}\")\n",
        "    assert False, \"Check quality\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "id": "Wz-10-69lmtW"
      },
      "outputs": [],
      "source": [
        "seqlen = inps[0].shape[1]\n",
        "hidden_size = inps[0].shape[2]\n",
        "seqlen, hidden_size\n",
        "\n",
        "\n",
        "def prepare_a(M: int):\n",
        "    inp = inps[0][0]\n",
        "    if M > seqlen:\n",
        "        n_repeats = M // seqlen + 1\n",
        "        return inp.repeat(n_repeats, 1)[:M]\n",
        "    else:\n",
        "        return inp[:M]\n",
        "\n",
        "\n",
        "def benchmark(M, provider, provider_funcs):\n",
        "    N = weight.shape[0]\n",
        "    K = weight.shape[1]\n",
        "\n",
        "    a = prepare_a(M).to(torch.float16).cuda()\n",
        "    b = weight.T.to(torch.float16)\n",
        "    assert a.shape == (M, K), f\"{a.shape} != {(M, K)}\"\n",
        "    assert b.shape == (K, N), b.shape\n",
        "\n",
        "    quantiles = [0.5, 0.2, 0.8]\n",
        "    ms, min_ms, max_ms = triton.testing.do_bench(lambda: provider_funcs[provider](a, b), quantiles=quantiles)\n",
        "    perf = lambda ms: ms # TFlops = 2 * M * N * K * 1e-12 / (ms * 1e-3)\n",
        "    return perf(ms), perf(max_ms), perf(min_ms)\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "H9BhixxhlmtW",
        "outputId": "8e4b0fce-220a-4fe8-c17a-c3377c066954"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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/vOf/yynpKTIFotFvuqqq+Ti4mL1TrqD3HrrrXKXLl1ko9EoZ2RkyBdccIE/HMly7F6X5jQOSLF8fcaNGyfn5OTIRqNRzsvLk8eNGyf/9NNP/udj+dr4fPTRR3L//v1lk8kk9+7dW37llVeCno+l380aWZZldWpXRERERJGJfZCIiIiIGmFAIiIiImqEAYmIiIioEQYkIiIiokYYkIiIiIgaYUAiIiIiaoQBiYiIiKgRBiQiIiKiRhiQiIiIiBphQCKimDdx4kRoNBrcddddJz139913Q6PRYOLEiR1/YkSkGgYkIiIA+fn5WLlyJerr6/2PORwOvPfee+jcubOKZ0ZEamBAIiICMGTIEOTn5+ODDz7wP/bBBx+gc+fOGDx4sIpnRkRqYEAiIjrh1ltvxfLly/3bb7zxBm655RYVz4iI1MKARER0wk033YQvv/wSBw8exMGDB/HVV1/hpptuUvu0iEgFerVPgIgoUmRkZOCSSy7Bm2++CVmWcckllyA9PV3t0yIiFTAgEREFuPXWWzFlyhQAwNKlS1U+GyJSCwMSEVGAsWPHwuVyQaPRYMyYMWqfDhGphAGJiCiATqfD3r17/feJKDYxIBERNWK1WtU+BSJSmUaWZVntkyAiIiKKJBzmT0RERNQIAxIRERFRIwxIRERERI0wIBERERE1woBERERE1AgDEhEREVEjDEhEREREjTAgERERETXCgERERETUCAMSERERUSMMSERERESN/H+0XCgKpDknugAAAABJRU5ErkJggg==",
            "text/plain": [
              "<Figure size 640x480 with 1 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "GeMV-performance-fp16:\n",
            "      M    cuBLAS    Triton\n",
            "0   1.0  0.042640  0.050672\n",
            "1   2.0  0.057344  0.051200\n",
            "2   4.0  0.057344  0.051200\n",
            "3   8.0  0.057344  0.051680\n",
            "4  16.0  0.057536  0.051680\n",
            "5  32.0  0.061472  0.052288\n",
            "6  64.0  0.062464  0.062064\n"
          ]
        },
        {
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAioAAAGwCAYAAACHJU4LAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAm4VJREFUeJzs3Xd4XMXVx/Hv9i5pV71b7mAwmN57IIQeWmimQ8D0DqH3EkqAlxY6CSWQUAOBADYOIfQABmMbXNX79n7vff+YSLawbMtG1kry+TyPH9jdu7uzbvvzzJkzJsMwDIQQQgghhiFzrgcghBBCCLEqElSEEEIIMWxJUBFCCCHEsCVBRQghhBDDlgQVIYQQQgxbElSEEEIIMWxJUBFCCCHEsGXN9QB+Dl3XaWpqwufzYTKZcj0cIYQQQgyAYRhEIhEqKiowm1c/ZzKig0pTUxPV1dW5HoYQQggh1kF9fT1VVVWrvWZEBxWfzweoD5qXl5fj0QghhBBiIMLhMNXV1b3f46szooNKz3JPXl6eBBUhhBBihBlI2YYU0wohhBBi2JKgIoQQQohhS4KKEEIIIYatEV2jMlCappHJZHI9DAHY7fY1bkUTQggheozqoGIYBi0tLQSDwVwPRfyP2Wymrq4Ou92e66EIIYQYAUZ1UOkJKSUlJbjdbmkKl2M9Dfqam5upqamRXw8hhBBrNGqDiqZpvSGlsLAw18MR/1NcXExTUxPZbBabzZbr4QghhBjmRm2xQE9NitvtzvFIxIp6lnw0TcvxSIQQQowEozao9JDlheFFfj2EEEKsjVEfVIQQQggxcklQEUIIIcSwJUFFCCGEEMOWBJVRZtasWZhMpt4fLpeLKVOm8Mgjj/S57oQTTuDggw9e4+s1NDRgt9vZZJNN+n38gw8+YI899iAQCOB2u5kwYQLHH3886XR6MD6OEEKIDZwElVFq/vz5NDc3M3fuXE4//XTOOOMM3nvvvbV+nSeffJIjjjiCcDjMJ5980uexuXPn8stf/pKtttqK2bNnM2fOHO677z7sdrvs6hFCbBAyWgbDMHI9jFFt1PZR6Y9hGMQz8SF/X7dt7ZrN6brO73//ex555BHq6+spLS3l9NNPZ8cdd2T33Xenu7ubgoICAL766iumTZvG4sWLGTNmTO9rlJSU9F5zzjnncO+99/Lll1+y5557DngchmHwxBNP8MADD1BVVcVjjz3Gtttu2/v4O++8Q1lZGbfffnvvfePGjeOXv/zlgN9DCCFGqoyWYX7nfDw2DzX5NVjMllwPaVTaoIJKPBPHe4t3yN83enkUj90z4Osvv/xy/vjHP3L33Xez00470dzczLx589bpvQ3D4O2332bZsmV9QsZAzJw5k3g8zl577UVlZSU77LADd999Nx6P+ixlZWU0Nzcze/Zsdtlll3UanxBCjFQd8Q4645100olmaNTm12KzSCPLwbZBBZWRIBKJ8Ic//IH777+f448/HlCzFDvttBOzZs0a8OtUVVUBkEql0HWd66+/fq3DxGOPPcZvfvMbLBYLm2yyCWPHjuXFF1/khBNOAODwww/n7bffZtddd6WsrIztttuOPffck+nTp5OXl7dW7yWEECNJKpuiKdKEx+7BZXXRGG4kq2epK6jDYXXkenijygYVVNw2N9HLozl534H6/vvvSaVSa7VE059//etf+Hw+UqkUn376KWeddRaBQIAzzjhjQM8PBoP87W9/48MPP+y979hjj+Wxxx7rDSoWi4UnnniCG2+8kffff59PPvmEm2++mdtuu41PP/2U8vLyn/UZhBBiuGqPtRPLxCj1lGIymSjxlNAWbSOrZRkbGLtWf++L1duggorJZFqrJZhccLlcq3zMbFa1zysWbvUcFfBTdXV1vTUqU6ZM4ZNPPuGmm24acFB59tlnSSaTfZaLDMNA13UWLFjAxIkTe++vrKzkuOOO47jjjuOGG25g4sSJPPTQQ1x33XUDei8hhBhJ4pk4TdEmvHZvb/2hxWyhxFtCe6ydHzp/YKx/LD6HL8cjHR1k188wM2HCBFwuV787dIqLiwFobm7uve+rr74a0OtaLBYSicSAx/HYY49x4YUX8tVXX/X++Prrr9l55515/PHHV/k8v99PeXk5sVhswO8lhBAjSWu0lWQ2idfet+bRbDJT4ikhko6woHMBwWQwNwMcZTaoGZWRwOl0cumll3LJJZdgt9vZcccdaW9v57vvvmP69OlUV1dz7bXXctNNN7FgwQLuvPPOfl+nra2NZDLZu/TzzDPPcNhhh/W5JhQKrRR0CgsL6ezs5Msvv+TPf/4zkydP7vP4UUcdxfXXX8+NN97IY489xldffcUhhxzCuHHjSCaTPP3003z33Xfcd999g/rzIoQQw0EkFaE12kqBs6Df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",
            "text/plain": [
              "<Figure size 640x480 with 1 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "GeMM-performance-fp16:\n",
            "          M    cuBLAS    Triton\n",
            "0    4096.0  1.134592  1.535680\n",
            "1    5120.0  1.394688  1.914752\n",
            "2    6144.0  1.840192  2.346016\n",
            "3    7168.0  2.203040  2.803680\n",
            "4    8192.0  3.158048  3.133008\n",
            "5    9216.0  2.916320  3.512736\n",
            "6   10240.0  3.172768  3.776800\n",
            "7   11264.0  3.486976  4.150928\n",
            "8   12288.0  3.938800  4.607584\n",
            "9   13312.0  4.487152  4.963056\n",
            "10  14336.0  4.813248  5.431008\n",
            "11  15360.0  5.482304  5.699728\n",
            "12  16384.0  5.670224  6.212784\n",
            "13  17408.0  6.602560  6.731712\n",
            "14  18432.0  6.869008  7.074528\n",
            "15  19456.0  6.758400  7.614688\n",
            "16  20480.0  6.984048  7.969312\n"
          ]
        }
      ],
      "source": [
        "configs = [\n",
        "    triton.testing.Benchmark(\n",
        "        x_names=[\"M\"],  # Argument names to use as an x-axis for the plot\n",
        "        x_vals=[2 ** i for i in range(7)],  # Different possible values for `x_name`\n",
        "        line_arg=\"provider\",  # Argument name whose value corresponds to a different line in the plot\n",
        "        # Possible values for `line_arg`\n",
        "        line_vals=[ref_lib.lower(), \"triton\"],  # Label name for the lines\n",
        "        line_names=[ref_lib, \"Triton\"],  # Line styles\n",
        "        styles=[(\"green\", \"-\"), (\"blue\", \"-\")],\n",
        "        ylabel=\"ms\",  # Label name for the y-axis\n",
        "        plot_name=\"GeMV-performance-\" + (\"fp16\"),  # Name for the plot, used also as a file name for saving the plot.\n",
        "        args={\"provider_funcs\": {\"triton\": matmul, \"cublas\": torch.matmul}},\n",
        "    ),\n",
        "    triton.testing.Benchmark(\n",
        "        x_names=[\"M\"],  # Argument names to use as an x-axis for the plot\n",
        "        x_vals=[1024 * i for i in range(4, 21)],  # Different possible values for `x_name`\n",
        "        line_arg=\"provider\",  # Argument name whose value corresponds to a different line in the plot\n",
        "        # Possible values for `line_arg`\n",
        "        line_vals=[ref_lib.lower(), \"triton\"],  # Label name for the lines\n",
        "        line_names=[ref_lib, \"Triton\"],  # Line styles\n",
        "        styles=[(\"green\", \"-\"), (\"blue\", \"-\")],\n",
        "        ylabel=\"ms\",  # Label name for the y-axis\n",
        "        plot_name=\"GeMM-performance-\" + (\"fp16\"),  # Name for the plot, used also as a file name for saving the plot.\n",
        "        args={\"provider_funcs\": {\"triton\": matmul, \"cublas\": torch.matmul}},\n",
        "    ),\n",
        "]\n",
        "\n",
        "\n",
        "triton.testing.perf_report(configs)(benchmark).run(show_plots=True, print_data=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "**Not bad!!!**"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [
        "2R9vd2NOlmtc"
      ],
      "gpuType": "T4",
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.3"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "3838c3cdee884f6d902e3b5d0d4e4583": {
          "model_module": "@jupyter-widgets/controls",
          "model_module_version": "1.5.0",
          "model_name": "HTMLModel",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_87caf0f9f3dc4b508d7733e66fde5407",
            "placeholder": "​",
            "style": "IPY_MODEL_c0e17b91d9b244daaf63f87f4683503e",
            "value": " 2/2 [00:37&lt;00:00, 17.49s/it]"
          }
        },
        "5f922ecfa23c4705a42336c1e79900df": {
          "model_module": "@jupyter-widgets/base",
          "model_module_version": "1.2.0",
          "model_name": "LayoutModel",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "61621d16e2cd40c3ad0c6a849a8b8264": {
          "model_module": "@jupyter-widgets/controls",
          "model_module_version": "1.5.0",
          "model_name": "HBoxModel",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HBoxModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HBoxView",
            "box_style": "",
            "children": [
              "IPY_MODEL_6bed81d4616649bd857418a131538f7d",
              "IPY_MODEL_9ffb3a0c0fea4081bf4f7c5e6218758f",
              "IPY_MODEL_3838c3cdee884f6d902e3b5d0d4e4583"
            ],
            "layout": "IPY_MODEL_7c3c03fa91874ae5ac941ed171eeb3ea"
          }
        },
        "6272889cfea04dab834ee1c62817540b": {
          "model_module": "@jupyter-widgets/controls",
          "model_module_version": "1.5.0",
          "model_name": "DescriptionStyleModel",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "6bed81d4616649bd857418a131538f7d": {
          "model_module": "@jupyter-widgets/controls",
          "model_module_version": "1.5.0",
          "model_name": "HTMLModel",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_7498bbbbae7048119584dea4a914da0a",
            "placeholder": "​",
            "style": "IPY_MODEL_6272889cfea04dab834ee1c62817540b",
            "value": "Loading checkpoint shards: 100%"
          }
        },
        "7498bbbbae7048119584dea4a914da0a": {
          "model_module": "@jupyter-widgets/base",
          "model_module_version": "1.2.0",
          "model_name": "LayoutModel",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "7c3c03fa91874ae5ac941ed171eeb3ea": {
          "model_module": "@jupyter-widgets/base",
          "model_module_version": "1.2.0",
          "model_name": "LayoutModel",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "7f1074b6abde4dddb7e50f67dc9a99b3": {
          "model_module": "@jupyter-widgets/controls",
          "model_module_version": "1.5.0",
          "model_name": "ProgressStyleModel",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "ProgressStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "bar_color": null,
            "description_width": ""
          }
        },
        "87caf0f9f3dc4b508d7733e66fde5407": {
          "model_module": "@jupyter-widgets/base",
          "model_module_version": "1.2.0",
          "model_name": "LayoutModel",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "9ffb3a0c0fea4081bf4f7c5e6218758f": {
          "model_module": "@jupyter-widgets/controls",
          "model_module_version": "1.5.0",
          "model_name": "FloatProgressModel",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "FloatProgressModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "ProgressView",
            "bar_style": "success",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_5f922ecfa23c4705a42336c1e79900df",
            "max": 2,
            "min": 0,
            "orientation": "horizontal",
            "style": "IPY_MODEL_7f1074b6abde4dddb7e50f67dc9a99b3",
            "value": 2
          }
        },
        "c0e17b91d9b244daaf63f87f4683503e": {
          "model_module": "@jupyter-widgets/controls",
          "model_module_version": "1.5.0",
          "model_name": "DescriptionStyleModel",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        }
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
